<?php
declare(strict_types=1);
namespace qhweb\phpsm\ecc\sm3;

use qhweb\phpsm\ecc\sm3\GeneralDigest;

/**
 * SM3 哈希算法实现（消息摘要，非加密解密）
 *
 * 符合 GM/T 0004-2012 标准，摘要长度为 256 位（32 字节）。
 * 本类继承自 GeneralDigest，提供 Update/BlockUpdate/DoFinal 等接口以处理字节流输入并输出定长摘要。
 *
 * 注意：类名含“加密解密”字样，但 SM3 为单向哈希函数，不具备解密能力。
 *
 * @package     qhweb\phpsm\ecc\sm3
 * @author      qhweb
 * @version     1.0.0
 */
class SM3Digest extends GeneralDigest
{
    /**
     * 算法名称
     * @var string
     */
    public static $AlgorithmName="SM3";

    /**
     * 摘要长度（字节）
     * @var int
     */
    private const DIGEST_LENGTH = 32;

    /**
     * 初始向量 IV（8 个 32 位字，大端）
     * @var int[]
     */
    private static $v0 = array(0x7380166f, 0x4914b2b9, 0x172442d7, -628488704,  -1452330820, 0x163138aa,-477237683,  -1325724082);
    /**
     * 当前链接变量 v（8 个 32 位字）
     * @var int[]
     */
    private $v = array(0, 0, 0, 0, 0, 0, 0, 0);
    /**
     * 临时工作变量 v_（8 个 32 位字）
     * @var int[]
     */
    private $v_ = array(0, 0, 0, 0, 0, 0, 0, 0);
    /**
     * 扩展消息字缓冲区 X（前 16 项预置为 0）
     * @var int[]
     */
    private static $X0 = array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    /**
     * 扩展消息字缓冲区 X（运行时）
     * @var int[]
     */
    private $X = array();
    /**
     * X 中已填充的字数（0–16）
     * @var int
     */
    private $xOff;
    /**
     * 轮常数 T（前 16 轮与后 48 轮）
     * @var int
     */
    private $T_00_15 = 0x79cc4519;
    private $T_16_63 = 0x7a879d8a;
    
    public function  GetDigestSize():int
    {
        return $this::DIGEST_LENGTH;
    }
    /**
     * 构造方法
     * 初始化父类状态，复制 IV，并重置内部缓冲与工作变量。
     */
    public function __construct()
    {
        parent::__construct(); 
       // parent::$gSM3Digest=$this;
        $this->Reset();
    }

    /**
     * 从另一个 SM3Digest 实例复制内部状态（用于分块/续算）
     * @param SM3Digest $t 源实例
     */
    public function setSM3Digest($t): void
    {
       parent::setGeneralDigest($t);
       $this->arraycopy($t->X, 0, $this->X, 0, sizeof($t->X));
         $this->xOff = $t->xOff;
        $this->arraycopy($t->v, 0, $this->v, 0, sizeof($t->v));
    }
    
    /**
     * 重置内部状态，准备新的哈希计算
     */
    public function Reset(): void
    {
        parent::Reset();
        $this->arraycopy(SM3Digest::$v0, 0, $this->v, 0, sizeof(SM3Digest::$v0));
        $this->xOff = 0;
        $this->arraycopy(SM3Digest::$X0, 0, $this->X, 0, sizeof(SM3Digest::$X0));
    }

    /**
     * 数组拷贝（工具方法）
     *
     * @param array $InBuf     源数组
     * @param int   $InBufPos  源起始位置
     * @param array &$OutBuf   目标数组（引用）
     * @param int   $OutBufPos 目标起始位置
     * @param int   $Len       拷贝长度
     */
    public static function arraycopy($InBuf, $InBufPos, &$OutBuf, $OutBufPos, $Len): void
    {
        for ($n = 0; $n < $Len; $n++) {
            $OutBuf[$n + $OutBufPos] = $InBuf[$n + $InBufPos];
        }
    }

	
    /**
     * 处理一个 512 位消息块（W 扩展、压缩函数）
     *
     * 步骤：
     * 1) 将 X[0..15] 扩展为 X[16..67]（P1 扩展）
     * 2) 生成 W'[i] = X[i] ^ X[i+4]（i=0..63）
     * 3) 64 轮压缩：SS1、SS2、TT1、TT2 与置换函数 FFj/GGj、P0/P1
     * 4) 与 IV 异或得到新的链接变量 v
     * 5) 重置 X 与 xOff
     */
    public function ProcessBlock(): void
    {
        $ww = $this->X;
        $ww_ = array();
        // 扩展消息字 W[16..67]
        for ($i = 16; $i < 68; $i++){
            $ww[$i] = $this->P1( $ww[ $i - 16] ^ $ww[$i - 9] ^ ($this->ROTATE($ww[$i - 3], 15))) ^ ($this->ROTATE($ww[$i - 13], 7)) ^ $ww[$i - 6];
        }
        // 生成 W'[i] = W[i] ^ W[i+4]（i=0..63）
        for ($i = 0; $i < 64; $i++){
            $ww_[$i] = $ww[$i] ^ $ww[$i + 4];
        }
        // 压缩函数主循环（前 16 轮与后 48 轮使用不同 FF/GG）
        $vv = $this->v;
        $vv_ = $this->v_;
        
        $this->arraycopy($vv, 0, $vv_, 0, sizeof($vv));
        
       // int SS1, SS2, TT1, TT2, aaa;
        for ($i = 0; $i < 16; $i++){
            $aaa = $this->ROTATE($vv_[0], 12);
            $SS1 = $this->MyAdd($aaa , $vv_[4]);
            $SS1 = $this->MyAdd($SS1, $this->ROTATE($this->T_00_15, $i));
            $SS1 = $this->ROTATE($SS1, 7);
            $SS2 = $SS1 ^ $aaa;
            
            $TT1=$this->FF_00_15($vv_[0], $vv_[1], $vv_[2]);
            $TT1=$this->MyAdd($TT1, $vv_[3]);
            $TT1=$this->MyAdd($TT1, $SS2);
            $TT1=$this->MyAdd($TT1, $ww_[$i]);
            
            $TT2=$this->GG_00_15($vv_[4], $vv_[5], $vv_[6]);
            $TT2=$this->MyAdd($TT2, $vv_[7]);
            $TT2=$this->MyAdd($TT2, $SS1);
            $TT2=$this->MyAdd($TT2, $ww[$i]);

            $vv_[3] = $vv_[2];
            $vv_[2] = $this->ROTATE($vv_[1], 9);
            $vv_[1] = $vv_[0];
            $vv_[0] = $TT1;
            $vv_[7] = $vv_[6];
            $vv_[6] = $this->ROTATE($vv_[5], 19);
            $vv_[5] = $vv_[4];
            $vv_[4] = $this->P0($TT2);
        }
        for ($i = 16; $i < 64; $i++){
            $aaa = $this->ROTATE($vv_[0], 12);
            $SS1 = $this->MyAdd($aaa , $vv_[4]);
            $z= $this->ROTATE($this->T_16_63, $i);
            $SS1 = $this->MyAdd( $SS1, $this->ROTATE($this->T_16_63, $i));
            $SS1 = $this->ROTATE($SS1, 7);
            $SS2 = $SS1 ^ $aaa;
            
            $TT1 = $this->MyAdd($this->FF_16_63($vv_[0], $vv_[1], $vv_[2]) , $vv_[3]);
            $TT1 = $this->MyAdd( $TT1, $SS2);
            $TT1 = $this->MyAdd( $TT1, $ww_[$i]);

            $TT2 = $this->MyAdd($this->GG_16_63($vv_[4], $vv_[5], $vv_[6]) , $vv_[7]);
            $TT2 = $this->MyAdd( $TT2, $SS1);
            $TT2 = $this->MyAdd($TT2 , $ww[$i]);
            $vv_[3] = $vv_[2];
            $vv_[2] = $this->ROTATE($vv_[1], 9);
            $vv_[1] = $vv_[0];
            $vv_[0] = $TT1;
            $vv_[7] = $vv_[6];
            $vv_[6] = $this->ROTATE($vv_[5], 19);
            $vv_[5] = $vv_[4];
            $vv_[4] = $this->P0($TT2);
        }
        // 更新链接变量：v = v ^ v_
        for ($i = 0; $i < 8; $i++){
            $vv[$i] ^= $vv_[$i];
        }
        $this->v=$vv;
        $this->v_=$vv_;
        
        // 重置缓冲
        $this->xOff = 0;
        $this->arraycopy(SM3Digest::$X0, 0, $this->X, 0, sizeof(SM3Digest::$X0));
    }

    /**
     * 处理一个 32 位字（大端）
     *
     * 从输入字节数组的指定偏移读取 4 个字节，按大端组合为 32 位整数，
     * 存入 X 缓冲；当收集满 16 个字时触发 ProcessBlock。
     *
     * @param array $in_Renamed 输入字节数组
     * @param int   $inOff      起始偏移（字节）
     */
    public function ProcessWord($in_Renamed, $inOff): void
    {
     
       $n = $this->LeftRotateLong($in_Renamed[$inOff] , 24);
       $n |= $this->LeftRotateLong(($in_Renamed[++$inOff] & 0xff) , 16);
       $n |= $this->LeftRotateLong(($in_Renamed[++$inOff] & 0xff) , 8);
       $n |= ($in_Renamed[++$inOff] & 0xff);

        $this->X[$this->xOff] = $n;
        
        if (++$this->xOff == 16)
        {
            $this->ProcessBlock();
        }
    }


    /**
     * 处理消息长度（比特长度，大端 64 位）
     *
     * 在填充阶段调用，将消息总长度（比特）写入 X[14..15]。
     * 若当前 xOff > 14，则先处理当前块。
     *
     * @param int $bitLength 消息总比特长度
     */
    public function ProcessLength($bitLength): void
    {
        if ($this->xOff > 14){
            $this->ProcessBlock();
        }
        // 高 32 位
        $this->X[14] =  ($this->RightRotateLong($bitLength, 32));
         // 低 32 位
        $this->X[15] = ($bitLength &  0xffffffff);
    }
    
    /**
     * 将 32 位整数按大端写入字节数组
     *
     * @param int   $n    待写入的整数
     * @param array &$bs   目标字节数组（引用）
     * @param int   $off  写入偏移（字节）
     */
    public function IntToBigEndian($n, &$bs, $off): void
    {
        $bs[$off] = ($this->RightRotateLong($n, 24)) & 0xff;
        $bs[++$off] = ($this->RightRotateLong($n, 16))  & 0xff;
        $bs[++$off] =  ($this->RightRotateLong($n, 8))  & 0xff;
        $bs[++$off] = ($n)  & 0xff ;
    }

   /**
     * 完成哈希计算，输出摘要
     *
     * 执行填充、处理最后一块、将 8 个 32 位字按大端写入输出缓冲，并重置内部状态。
     *
     * @param array &$out_Renamed 输出缓冲（长度至少 DIGEST_LENGTH）
     * @param int   $outOff      输出偏移（字节）
     * @return int 实际写入的字节数（固定为 DIGEST_LENGTH）
     */
    public function DoFinal(&$out_Renamed, $outOff): int
    {
        $this->Finish();
        
        for ($i = 0; $i < 8; $i++) {
            $this->IntToBigEndian($this->v[$i], $out_Renamed, $outOff + $i * 4);
        }
        
        $this->Reset();
        
        return $this::DIGEST_LENGTH;
    }

    /**
     * 32 位整数符号扩展到无符号（模拟 Java int 行为）
     *
     * @param int $lValue 输入值
     * @return int 32 位无符号结果
     */
    private function HandleSign($lValue): int
    {
        $lValue = $lValue & 0xFFFFFFFF;
        if($lValue>=0x80000000)
        {
            $lValue=$lValue-(0xffffffff+1);
        }
        return  $lValue;
    }

    /**
     * 32 位模 2^32 加法（带符号扩展处理）
     *
     * @param int $A 加数
     * @param int $B 加数
     * @return int 和（模 2^32）
     */
    private function MyAdd($A, $B): int
    {
        $lValue=$this->HandleSign($A) + $this->HandleSign($B);
        return  $this->HandleSign($lValue);
    }
    
	/**
     * 32 位循环左移
     *
     * @param int $lValue 待旋转值
     * @param int $lBits  左移位数（0–31）
     * @return int 旋转结果
     */
    public function LeftRotateLong($lValue, $lBits): int
	{
        $lBits = $lBits % 32;
        $lValue=$lValue<< $lBits;
        $lValue=$lValue & 0xffffffff;
        if($lValue>=0x80000000){
            $lValue=$lValue-(0xffffffff+1);
        }
        return  $lValue;
	}
	
	
	/**
     * 32 位循环右移
     *
     * @param int $lValue 待旋转值
     * @param int $lBits  右移位数（0–31）
     * @return int 旋转结果
     */
    private function RightRotateLong($lValue, $lBits): int
	{
	    $mValue =0;
	   
	    $mValue = $lValue;
      // $lBits = $lBits % 32;

        if( $lBits == 0 ) {
           return $mValue ;
        }

        if ($lValue >= 0){
            if($lBits<0) $lBits= 32+ $lBits;
            $r = $lValue >> $lBits;
        }else{
            $t=~$lBits;
            if($t<0) $t= 32+ $t;
            $r= ($lValue >>  $lBits) + (2 << $t);
        }
        return $r;
	}
    
    /**
     * 根据轮次选择 FF 函数
     *
     * @param int $X X
     * @param int $Y Y
     * @param int $Z Z
     * @param int $j 轮次（0–63）
     * @return int FF(X,Y,Z)
     */
    private function FFj($X, $Y, $Z, $j): int
    {
        return ($j >= 0 && $j <= 15) ? $this->FF_00_15($X, $Y, $Z) : $this->FF_16_63($X, $Y, $Z);
	}
    /**
     * 根据轮次选择 GG 函数
     *
     * @param int $X X
     * @param int $Y Y
     * @param int $Z Z
     * @param int $j 轮次（0–63）
     * @return int GG(X,Y,Z)
     */
    private function GGj($X, $Y, $Z, $j): int
    {
        return ($j >= 0 && $j <= 15) ? $this->GG_00_15($X, $Y, $Z) : $this->GG_16_63($X, $Y, $Z);
	}

    private function ROTATE($X, $n):int
    {
        return $this->LeftRotateLong($X , $n) | ($this->RightRotateLong($X, (32 - $n)));
    }
    
    private function P0($X):int
    {
        $a=$this->ROTATE(($X), 9);
        $b= $this->ROTATE(($X), 17);
        return (($X) ^ $this->ROTATE(($X), 9) ^ $this->ROTATE(($X), 17));
    }
    
    private function P1($X):int
    {
        return (($X) ^ $this->ROTATE(($X), 15) ^ $this->ROTATE(($X), 23));
    }
    
    private static function FF_00_15($X, $Y, $Z):int
    {
        return ($X ^ $Y ^ $Z);
    }
    
    private static function FF_16_63($X, $Y, $Z):int
    {
        return (($X & $Y) | ($X & $Z) | ($Y & $Z));
    }
    
    private static function GG_00_15($X, $Y, $Z):int
    {
        return ($X ^ $Y ^ $Z);
    }
    
    private static function GG_16_63($X, $Y, $Z):int
    {
        return (($X & $Y) | (~ $X & $Z));
    }
}